By: Allyson Lynch

In [1]:
import pandas as pd
import csv

Pull the name of the amine(s) in a CCDC crystal

In [2]:
#helper functions to check amine name
def not_metal(c):
    metals_df = pd.read_table("Outputs/01 CCDC Smiles File/Input Files/Metals.tsv")
    metals=[True if c.find(metal.lower())==-1 else False for metal in metals_df.columns[1:]]
    return all(metals)
   
def is_strict_organic(c):
    stricts = ["cyano", "fluoro", "chlor", "bromo", "iod", "bor", "ars", "nitro", "nitra", "nitrile", "ars", "sil", "phosph", "sulf", "sel", "thio", "thia", "thie", "oxa", "oxy", "none", "dimetham", "glycol", "azido", "oxide"] 
    organic = [True if c.find(strict)==-1 else False for strict in stricts]
    return all(organic)
    
def is_amine(c):
    ams = ["ium", "amin", "ammon", "ine", "ammin", "az", "pyrimid", "nitrilo", "phyrin", "pyrrol", "isoindole", "anil", "ptilocaulin", "phycene", "annulene", "metformin", "pyrid"]
    amines = [False if c.find(am)!=-1 else True for am in ams]
    if all(amines)!=True:
            return True
In [3]:
#helper functions to clean amine name
def reformat(mlc):
    for symbol in ["!", "$", "catena"]: #clean extra symbols  
        mlc = mlc.replace(symbol, "") 
    if mlc[0]=="-": 
        mlc = mlc.replace("-", "", 1)
    if mlc.find("isosparteine")==-1:
        for descriptors in ["(r)-", "r-", "(s)-", "-s-", "(s/r)-", "(r/s)-", "(r,r)-", "(r,s)-", "rac-", "rctt-", "(1r,5s)-", "(+)-", "(-)-", "(+-)-", "trans-", "cis-", "trans,", "cis,", "iv-", "(l)-", "(d)-", "meso-", "anti-", "syn-", "hemi-", "endo-", "hemi(", "rtct-", "alpha-", "beta-"]:
            mlc = mlc.replace(descriptors, "") 
    if mlc.find("dec-")==-1:
        mlc = mlc.replace("c-", "")
    while (mlc.count("(")>mlc.count(")")) or (mlc.count("[")>mlc.count("]")): #adjust parenthesis (remove uneven and non-internal)
        mlc = mlc[1:] 
    while (mlc.count("(")<mlc.count(")")) or (mlc.count("[")<mlc.count("]")):
        mlc = mlc[:-1] 
    if ((mlc.rfind("(")!=mlc.find("(")) or mlc.count("(")==1) or ((mlc.rfind("[")!=mlc.find("[")) or mlc.count("[")==1):
        while mlc[0]=="(" and mlc[-1]==")": 
            mlc = mlc[1:-1]
    prefixes_df = pd.read_table("Outputs/01 CCDC Smiles File/Input Files/Prefixes.tsv")
    prefixes = prefixes_df.columns[1:].tolist()
    prefixes.append("bis")
    for prefix in prefixes: #remove extra prefixes
        while mlc.find(prefix)!=-1 and ((mlc.find(prefix)+len(prefix))==mlc.find("(") and mlc.rfind(")")+1==len(mlc)) or ((mlc.find(prefix)+len(prefix))==mlc.find("[") and mlc.rfind("]")+1==len(mlc)):
            mlc = mlc.replace(prefix, "", 1)
            if (mlc[0]=="(" and mlc[-1]==")") or (mlc[0]=="[" and mlc[-1]=="]"): #fix parenthesis
                mlc = mlc[1:-1]
    if mlc[:2]=="s-":
        mlc = mlc.replace("s-", "")
    for chirality in ["rs", "sr", "ars", "ar", "br"]: #remove chirality
        if mlc.find(chirality)!=-1 and (mlc[mlc.index(chirality)-1].isdigit() or mlc[mlc.index(chirality)-1]=="("):
            mlc = mlc[mlc.index(")-")+2:]
    for n,N in [["n-", "N-"], ["n,", "N,"], ["n'", "N'"], ["n6", "N6"]]: #recapitalize Nitrogen
        if mlc.find("tetra-n-butylammonium")==-1 and mlc.find("tetra-n-propylammonium")==-1:
            mlc = mlc.replace(n,N)
    for i in range(1,10):
        if mlc.find("tetra-n-butylammonium")==-1 and mlc.find("tetra-n-propylammonium")==-1:
            mlc = mlc.replace("n"+str(i), "N"+str(i))
    return mlc
    
def neutralize(comp):
    if comp.find("methyl")==-1 and comp.find("diethyl")==-1 and comp.find("tri")==-1:
        for ethyl in ["ethylene-1,2-diammonium","ethylene-1.2-diammonium", "1,2-ethylenediammonium", "ethylene-1,2-diamine", "1,2-ethylenediamine", "1,2-ethyldiammonium", "N,N'-ethylenediamine", "ethylenediamine", "2-aminoethylammonium", "1-2,diaminoethane"]:
            comp = comp.replace(ethyl,"ethane-1,2-diamine")
    if comp.find("butyl")==-1:
        comp = comp.replace("tyl-1", "tane-1")
    if comp.find("tetra")==-1 and comp.find("tetrazole")!=1: #maintain quaternary ammonium
        if comp.find("ammonium")!=-1:
            if comp[:comp.index("ammonium")+8]!=comp:
                comp = comp.replace("ammonium", "amino")
        for old, new in [["ammonium", "amine"], ["pyridinio", "pyridyl"], ["imidazolium", "imidazole"], ["azepanium","azepane"], ["metforminium","metformin"]]: #exceptions
            comp = comp.replace(old, new)
        for ium in ["5,11-di-dium", "1,10-dium", "1,10-diium", "10-ium", "11-ium", "21-ium"]:
            comp = comp.replace(ium, "1-ium")
        for ium in ["'-di-ium", "-di-ium", "di-ium", "'''-diium", "'-diium", "-diium", "diium", "'-dium"]: 
            comp = comp.replace(ium, "-dium")    
        if comp.find("-dium")!=-1 and comp[comp.find("-dium")-1].isdigit(): #remove -#,#-dium
            if comp[comp.index("-dium")-5]=="e":
                comp = comp[:comp.index("-dium")-4] + comp[comp.index("-dium")+5:] 
            else:
                comp = comp[:comp.index("-dium")-4] + "e" + comp[comp.index("-dium")+5:]
        else: 
            comp = comp.replace("-dium", "")
        if comp.find("-ium")!=-1 and comp[comp.find("-ium")-1].isdigit(): #remove -#-ium
            if comp[comp.index("-ium")-3]=="e":
                comp = comp[:comp.index("-ium")-2] + comp[comp.index("-ium")+4:] 
            else:
                comp = comp[:comp.index("-ium")-2] + "e" + comp[comp.index("-ium")+4:]
        comp = comp.replace("inium", "ine")
        comp = comp.replace("ium", "ine")
        comp = comp.replace("pyridyle", "pyridyl")
    for old,new in [["diguanidine","guanidine"], ["dipyridine","pyridine"], ["dipyrazine", "pyrazine"], ["dipiperidine", "piperidine"], ["tetrapyridinium", "pyridine"], ["hexapyridine", "pyridine"], ["tripiperidine", "piperidine"], ["triguanidine", "guanidine"], ["tetraguanidinium", "guanidine"], ["tetrapiperidine", "piperidine"], ["tetrapiperidinium", "piperidine"], ["dodeca-guanidine", "guanidine"], ["dipyrrolide", "pyrrol"], ["bipyridine", "pyridine"], ["dipyrazoline", "pyrazole"]]: #remove extra prefixes
        if comp==old: 
            comp = new
    for find,replace in [["aguanidine","guanidine"], ["tetraethylenepent","tetraethylenepentamine"], ["apiperidine", "piperidine"], ["triethylenetetra", "triethylenetetra-amine"], ["dipiperaziN", "piperazine"]]:
        if comp.find(find)!=-1:
            comp = replace
    return comp
In [4]:
#helper functions to edit name
def substitutions(m):
    substitutions_df = pd.read_table("Outputs/01 CCDC Smiles File/Input Files/Substitutions.tsv")
    olds = substitutions_df['Old'].tolist()
    fix = substitutions_df['Fix'].tolist()
    for old in olds:
        m = m.replace(old, fix[olds.index(old)])
    return m

def typos(m):
    typos_df = pd.read_table("Outputs/01 CCDC Smiles File/Input Files/Problematic_structures - Errors.tsv")
    typos = typos_df['Error'].tolist()
    fix = typos_df['Fix'].tolist()
    for typo in typos:
        m = m.replace(typo, fix[typos.index(typo)])
    return m

def combine(m):
    for split in ["(arsenate)","dimethylammonium)-", "cane)-dec"]: #split molecule fragments 
        if m.find(split)!=-1: 
            m = m[:m.find(")")+1]
    for split in ["hexane)-", "octane)-", ")decane)-", "ium))-"]:
        if m.find(split)!=-1:
            m = m[:m.find(split[:-1])+(len(split)-1)]
    if m.find("ium)(mu")!=-1:
        m = m[:m.find("ium)(mu")+4]
    for find,replace in [["5 !", "5"], ["ium)-hexa", "ium) hexa"], ["ium)-tetra", "ium) tetra"], ["benzene)-tris", "benzene) tris"]]:
        m = m.replace(find, replace)
    if m.find("n-oxide)")!=-1: #combine molecule fragments
        m = m[:m.index("n-oxide)")-1] + "-" + m[m.index("n-oxide)"):]
    if m.find("oxyl 3")!=-1: 
        m = m[:m.index("oxyl 3")+4] + "-" + m[m.index("oxyl 3")+5:]
    for close in ["diphenylamine perchlorate","hydrogen boro", "amine oxide", "2-aminopyridine n", "nyl) s","-yl) s", "yl) ethylene", "sulfonic acid", "acid deoxy", "nium n-o", "16) ox", "methyl ammonium", "diium octaphyrin", "-ium n", "dine 3-", "ine 1-"]:
        while m.find(close)!=-1:
            stop = close.index(" ")
            m = m[:m.index(close)+len(m[:stop])] + m[m.index(close)+len(m[:stop])+1:]
    return m

def manual_smiles(clean):
    problems_df = pd.read_table("Outputs/01 CCDC Smiles File/Input Files/organics_from_structures.tsv", engine='python', error_bad_lines=False)
    problems = problems_df['Amine'].tolist()
    smiles = problems_df['SMILES'].tolist()
    for problem in problems:
        if str(clean)==problem:
            clean = smiles[problems.index(problem)]
    return clean
In [5]:
def clean_name(crystal, exclusion_files=False):
    """takes a crystal and returns the clean amine name
    >>>clean_name("catena-((mu!12$-silicato)-octakis(mu!3$-oxo)-octadecakis(mu!2$-oxo)-dodecaoxo-dodeca-molybdenum-di-vanadium hemikis(triethylamine) bis(ethylenediamine) tetrahydrate) )")
    > [ethylenediamine, triethylamine]
    >>>clean_name("tris(Metforminium) bis(mu!6$-oxo)-tetrakis(mu!3$-oxo)-tetradecakis(mu!2$-oxo)-octa-oxo-deca-vanadium(v) octahydrate")
    > [amino[(diaminomethylene)amino]-n,n-dimethylmethaniminium]
    >>>clean_name("catena-((Dimethylammonium)-hexakis(mu!2$-formato-O,O')-iron(iii)-iron(ii))")
    > [dimethylamine]
    >>>clean_name("catena-(Triethylenetetra-ammonium tetrakis(mu!3$-hydrogen phosphato-O,O',O'')-bis(mu!2$-hydrogen phosphato-O,O')-tetra-zinc)")
    > [triethylenetetra-amine]
    >>>clean_name("3-Azido-1,2,4-triazolium nitrate")
    > []
    >>>clean_name("rctt-1,3-bis(4-Pyridinio)-2,4-bis(2-pyridinio)cyclobutane bis(mu!5$-oxo)-tetrakis(mu!3$-oxo)-hexakis(mu!2$-oxo)-tetradecaoxo-octa-molybdenum")
    > [1,3-bis(4-pyridinio)-2,4-bis(2-pyridinio)cyclobutane]
    >>>clean_name("2,7,11,18-Tetraethyl-3,6,12,17-tetramethylcorrphycene diperchlorate")
    > [2,7,11,18-tetraethyl-3,6,12,17-tetramethylcorrphycene]
    >>>clean_name("catena-[(1-methylhydrazin-1-ium) tris(mu-formato)-iron(ii)]")
    > [1-methylhydrazine]
    >>>clean_name("1,2-bis(2'-Tetrahydropyrimidyl)ethane dihydrogenpyrophosphate monohydrate")
    > [1,2-bis(2'-tetrahydropyrimidyl)ethane]
    >>>clean_name("N-t-Butyl-retinylideniminium perchlorate")
    > [N-t-butyl-reTinylideniminium]
    >>>clean_name("catena-[bis(trans-1,3-bis(Ammoniomethyl)cyclohexane)-tetrakis(mu!3$-phosphonato)-tri-zinc]")
    > [1,3-bis(aminomethyl)cyclohexane]
    >>>clean_name("tris(ethane-1,2-diaminium) bis(mu!9$-arsenato)-hexatriacontakis(mu!2$-oxo)-octadecaoxo-octadeca-tungsten ethylenediamine solvate nonahydrate")
    > [ethane-1,2-diamine]
    >>>clean_name("catena-((mu!2$-Ethylenediamine)-nonakis(mu!2$-oxo)-hexa-boron)") 
    > []
    >>>clean_name("tris(Piperazindium) hexakis(mu!3$-hydroxo)-(mu!2$-hydroxo)-pentakis(mu!2$-oxo)-dodecaoxo-chromium-hexa-molybdenum disulfate dodecahydrate")
    > [piperazine]
    >>>clean_name("3,6,7-triamino-7H-[1,2,4]triazolo[4,3-b][1,2,4]triazol-2-ium perchlorate")
    > [3,6,7-triamino-7h-[1,2,4]triazolo[4,3-b][1,2,4]triazole]
    >>>clean_name("4,4'-Bipyridyl phenylphosphonic acid")
    > [4,4'-bipyridyl]
    >>>clean_name("2,2'-quinoxalin-2,3-diyldipyridinium dinitrate")
    > []
    >>>clean_name("catena-(bis(1-Aminomethyl-7-aza-1,3,5-azoniatricyclo(3.3.1.1$3,7!)decane)-(mu!2$-oxo)-hexacosaoxooctamolybdate hexahydrate)")
    > [bis(1-aminomethyl-7-aza-1,3,5-azatricyclo(3.3.1.13,7)decane)]
    >>>clean_name("N,N'-Diphenylanthraquinonedi-iminium diperchlorate chlorobenzene trifluoroacetic acid solvate")
    > [c1ccc(cc1)N=C2c3ccccc3C(=Nc4ccccc4)c5ccccc25] 
    >>>clean_name("catena-(Ethylene-1,2-diammonium tetrakis(mu!4$-phosphato)-bis(mu!3$-oxo)-(mu!2$-fluoro)-penta-gallium clathrate)")
    > [1,2-diaminoethylene]
    >>>clean_name("hexakis(Imidazolium) hexakis((mu!3$-oxo)-(mu!2$-oxo)-dioxo-tungsten)-tellurate imidazole solvate")
    > [imidazole]
    """
    if exclusion_files:
        directory = "Outputs/01 CCDC Smiles File/exclusion_files/"
        met = open(directory + "not_metal.tsv", "a")
        metal_file = csv.writer(met, delimiter = '\t')
        org = open(directory + "is_strict_organic.tsv", "a")
        organic_file = csv.writer(org, delimiter = '\t')
        am = open(directory + "is_amine.tsv", "a")
        amines_file = csv.writer(am, delimiter = '\t')
        sim = open(directory + "is_simple_amine.tsv", "a")
        simple_file = csv.writer(sim, delimiter = '\t')
    fixed=set()
    crysta = crystal.lower()
    cryst = combine(crysta)
    crys = typos(cryst)
    for c in crys.split(): #split molecules by spaces
        m = substitutions(c)
        if not_metal(m): #remove metals
            if is_strict_organic(m): #strict organic
                if is_amine(m): #check amine
                    ref = reformat(m) #clean
                    n = neutralize(ref) #convert to neutral form
                    final = manual_smiles(n)
                    if final!="amine" and final!="diamine" and final!="bisamine" and final!="triamine" and final!="tetramine" and final!="ammonia" and final!="hydrazine":
                        if str(final).find("a-amine")==-1 or str(final).find("ethyl")!=-1:
                            fixed.add(final) #account for multiple possible amine
                        else:
                            if exclusion_files:
                                simple_file.writerow([crystal,final]) 
                    else:
                        if exclusion_files:
                            amines_file.writerow([crystal,m])
                else:
                    if exclusion_files:
                        organic_file.writerow([crystal,m])
            else:
                if exclusion_files:
                    metal_file.writerow([crystal,m])
    end = list(set(fixed)) #remove duplicates
    if exclusion_files:
        met.close()
        org.close()
        am.close()
        sim.close()
    return end


def clean_crystal(crystal_file, file_name, exclusion_files=False):
    """takes a file of crystals and returns a file of the refcodes, crystals, and clean amine names"""
    crystal_df = pd.read_table(crystal_file) #read file
    index = crystal_df['[REFCODE]'].tolist()
    crystal_df.set_index('[REFCODE]', inplace=True)
    prior = "x" #initialize
    for code in crystal_df.index.tolist(): #remove duplicates
        if code[:6]==prior:
            crystal_df.drop(code, inplace=True)
        prior = code[:6]
    #crystal_df = crystal_df.iloc[582:583]
    if exclusion_files:
        crystal_df['Amine']= crystal_df['[_chemical_name_systematic]'].apply(clean_name, exclusion_files=True)
    else:
        crystal_df['Amine']= crystal_df['[_chemical_name_systematic]'].apply(clean_name)
    final_df = crystal_df.loc[:, ['[_chemical_name_systematic]', 'Amine']] #simplify dataframe
    #print(final_df)
    filename = "Outputs/01 CCDC Smiles File/" + file_name
    final_df.to_csv(filename, sep='\t') #save as tsv

Convert the amine name to smiles strings

In [6]:
import urllib.request 
import urllib.error
import urllib.parse
from rdkit import Chem 
In [7]:
#helper functions to edit smiles strings
def cactus(name):
    """takes an amine name and calls a smiles string from cactus"""
    final = set()
    for amine in name:
        compliant_name = urllib.parse.quote_plus(amine)
        url = "https://cactus.nci.nih.gov/chemical/structure/"+amine+"/"+"smiles"
        req = urllib.request.Request(url)
        try: resp = urllib.request.urlopen(req)
        except urllib.error.URLError as e:
            resp = None
        if resp=="" or resp==None:
            resp = amine
        else:
            resp = resp.read().decode(resp.headers.get_content_charset() or 'utf-8')
            if resp.find("!")!=-1: #cannot convert from rdkit smiles back to cactus smiles
                resp = amine
        final.add(resp)
    end = list(set(final)) #remove duplicates
    return end
In [8]:
def check_name_cactus(name):
    """takes an amine name and returns a neutralized and canonicalized smiles string"""
    name_list = eval(name)
    resp = cactus(name_list) #pull name from cactus
    neutralize = []
    for amine in resp:
        amine = amine.replace("[N-]", "[NH]")
        amine = amine.replace("[n-]", "[nH]")
        for salt in [".[Cl-]", ".[H+]", "[H+].", "[Cl-].", ".Cl", "H+", "H2+", "H3+"]: #clear salt and final neutralize
            amine = amine.replace(salt, "") 
            amine = amine.replace("[N]", "N") #clean
            amine = amine.replace("[n]", "n")
        mol = Chem.MolFromSmiles(amine) #canonicalize
        if mol!=None:
            amine = Chem.MolToSmiles(mol, isomericSmiles=False)
        neutralize.append(amine)
    final = cactus(neutralize) #re-run through cactus to clean
    return final  
    
    
def smiles_file(amines_file, file_name):
    """takes a file of crystals and amine names and returns a file of the refcodes and clean smiles strings"""
    smiles_df = pd.read_table(amines_file) #read file
    #smiles_df = smiles_df.iloc[5353:5354]
    clean_df = smiles_df.loc[smiles_df['Amine'] != '[]'] #exlude empty lists
    clean_df['smiles'] = clean_df['Amine'].apply(check_name_cactus) #convert to smiles
    repeat_df = pd.DataFrame(clean_df.set_index('[REFCODE]').smiles.apply(pd.Series).stack().reset_index(level=-1, drop=True)) #split crystal amines with repeating REFCODE
    merge_df = pd.merge(repeat_df, clean_df, left_index=True, right_on='[REFCODE]').drop("smiles", axis=1).rename(columns={0:'smiles'}) #reformat columns after split
    inchi_df = pd.read_table("Outputs/01 CCDC Smiles File/Input Files/standardize_names.tsv") #read file
    final_df = pd.merge(merge_df, inchi_df, on="smiles",how='outer').set_index('[REFCODE]')
    final_df.drop(['source', 'stdinchi'], axis=1, inplace=True)
    filename = "Outputs/01 CCDC Smiles File/" + file_name
    final_df.to_csv(filename, sep='\t') #save as tsv
In [9]:
def ccdc_smiles_file(amine_list, smiles=True, exclusion_files=False):
    """takes a list of amine names and creates files of the cleaned amines and smiles strings"""
    for amine in amine_list:
        if amine=="oxides":
            exclusion_files=True
        clean_crystal("Data/CCDC/"+amine+" full.tab", "Amines_"+amine.replace(" ", "")+".tsv", exclusion_files)
        if smiles:
            smiles_file("Outputs/01 CCDC Smiles File/"+"Amines_"+amine.replace(" ", "")+".tsv", "Amines_"+amine.replace(" ", "")+"_smiles.tsv")

ccdc_smiles_file(["oxides", "metal oxides", "metal borates", "metal formates", "metal halides", "metal phosphonates", "metal peroxides", "metal oxalates"])        
/usr/local/lib/python3.5/dist-packages/ipykernel_launcher.py:26: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy